package cd.cdyb.sms.service.impl;

import cd.cdyb.sms.service.RedisService;
import cd.cdyb.sms.utils.CacheUtils;
import cd.cdyb.sms.utils.CollectionUtilPlus;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service("redisService")
public class RedisServiceImpl implements RedisService {


    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * String（字符串）操作类
     */
    @Resource(name = "redisTemplate")
    private ValueOperations<String, Object> valueOps;

    /**
     * List（列表）操作类
     */
    @Resource(name = "redisTemplate")
    private ListOperations<String, Object> listOps;

    /**
     * Set（集合）操作类
     */
    @Resource(name = "redisTemplate")
    private SetOperations<String, Object> setOps;

    /**
     * SortedSet（有序集合）操作类
     */
    @Resource(name = "redisTemplate")
    private ZSetOperations<String, Object> zSetOps;

    /**
     * Hash（哈希表）操作类
     */
    @Resource(name = "redisTemplate")
    private HashOperations<String, String, Object> hashOps;

    @Override
    public Object vGet(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return null;
        }
        return valueOps.get(cacheName.getPrefix().concat(key));
    }

    @Override
    public Object vGet(String key) {
        return valueOps.get(key);
    }

    @Override
    public List<Object> vGetAll(CacheUtils.CacheName cacheName) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return null;
        }
        Set<String> cacheKeys = redisTemplate.keys(cacheName.getRegex());
        if (CollectionUtilPlus.isNotNullOrEmpty(cacheKeys)) {
            return valueOps.multiGet(cacheKeys);
        } else {
            return null;
        }
    }

    @Override
    public List<Object> vMultiGet(CacheUtils.CacheName cacheName, boolean isRedisKey, String... keys) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return null;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(keys)) {
            return null;
        }
        Set<String> cacheKeys = new HashSet<>();
        for (String key : keys) {
            if (isRedisKey) { //redis中的key
                cacheKeys.add(key);
            } else { //组成key的变量
                cacheKeys.add(cacheName.getPrefix().concat(key));
            }
        }
        List<Object> result = valueOps.multiGet(cacheKeys);
        return result;
    }

    @Override
    public void vPut(CacheUtils.CacheName cacheName, String key, Object value) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return;
        }
        if (cacheName.getExpiration() > 0) {
            valueOps.set(cacheName.getPrefix().concat(key), value, cacheName.getExpiration(), TimeUnit.SECONDS);
        } else {
            valueOps.set(cacheName.getPrefix().concat(key), value);
        }
    }

    @Override
    public void vPut(String key, Object value, Integer expire) {
        if (expire > 0) {
            valueOps.set(key, value, expire, TimeUnit.SECONDS);
        } else {
            valueOps.set(key, value);
        }
    }

    @Override
    public void vPutAll(CacheUtils.CacheName cacheName, Map<String, Object> keyValueMap) {
/*        if (cacheName.getType() != CacheUtils.CacheType.V || MapUtilPlus.isEmpty(keyValueMap)) {
            return;
        }
        for (Map.Entry<String, Object> entry : keyValueMap.entrySet()) {
            vPut(cacheName, entry.getKey(), entry.getValue());
        }*/
    }

    @Override
    public Long vIncrement(CacheUtils.CacheName cacheName, String key, Long value) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = valueOps.increment(cacheKey, value == null ? 1L : value);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Long vIncrement(String key, Long value) {
        Long result = valueOps.increment(key, value == null ? 1L : value);
        Long expire = redisTemplate.getExpire(key);
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        return result;
    }

    @Override
    public Long vDecrement(CacheUtils.CacheName cacheName, String key, Long value) {
        if (cacheName.getType() != CacheUtils.CacheType.V) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = valueOps.increment(cacheKey, value == null ? -1L : -value);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }


    @Override
    public Long sPut(CacheUtils.CacheName cacheName, String key, Object... values) {
        if (cacheName.getType() != CacheUtils.CacheType.S) {
            return 0L;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(values)) {
            return 0L;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = setOps.add(cacheKey, values);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Boolean sExist(CacheUtils.CacheName cacheName, String key, Object value) {
        if (cacheName.getType() != CacheUtils.CacheType.S) {
            return false;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return setOps.isMember(cacheKey, value);
    }

    @Override
    public Long sRemove(CacheUtils.CacheName cacheName, String key, Object... values) {
        if (cacheName.getType() != CacheUtils.CacheType.S) {
            return null;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(values)) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = setOps.remove(cacheKey, values);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Boolean sMove(CacheUtils.CacheName sourceCacheName, String sourceKey, CacheUtils.CacheName destinationCacheName, String destinationKey, Object value) {
        if (sourceCacheName.getType() != CacheUtils.CacheType.S) {
            return false;
        }
        if (destinationCacheName.getType() != CacheUtils.CacheType.S) {
            return false;
        }
        String sourceCacheKey = sourceCacheName.getPrefix().concat(sourceKey);
        String destinationCacheKey = destinationCacheName.getPrefix().concat(destinationKey);
        Boolean result = setOps.move(sourceCacheKey, value, destinationCacheKey);
        if (destinationCacheName.getExpiration() > 0) {
            redisTemplate.expire(destinationCacheKey, destinationCacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Long sSize(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.S) {
            return 0L;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return setOps.size(cacheKey);
    }

    @Override
    public List<Object> sRandomMembers(CacheUtils.CacheName cacheName, String key, Long count) {
        if (cacheName.getType() != CacheUtils.CacheType.S || count == null) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return setOps.randomMembers(cacheKey, count);
    }

    @Override
    public Set<Object> sGetAll(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.S) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return setOps.members(cacheKey);
    }

    @Override
    public Set<Object> zGet(CacheUtils.CacheName cacheName, String key, Pageable pageable, boolean isAsc) {
        if (cacheName.getType() != CacheUtils.CacheType.Z) {
            return null;
        }
        long start = pageable.getOffset();
        long end = pageable.getOffset() + pageable.getPageSize() - 1;
        if (isAsc) {
            return zSetOps.range(cacheName.getPrefix().concat(key), start, end);
        } else {
            return zSetOps.reverseRange(cacheName.getPrefix().concat(key), start, end);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set<Object> zGetByScore(CacheUtils.CacheName cacheName, String key, boolean includeMin, Double minScore, boolean includeMax, Double maxScore, Pageable pageable, boolean isAsc) {
        if (cacheName.getType() != CacheUtils.CacheType.Z) {
            return null;
        }

        final RedisZSetCommands.Range range = RedisZSetCommands.Range.range();
        if (minScore != null) {
            if (includeMin) {
                range.gte(minScore);
            } else {
                range.gt(minScore);
            }
        }

        if (maxScore != null) {
            if (includeMax) {
                range.lte(maxScore);
            } else {
                range.lt(maxScore);
            }
        }

        final RedisZSetCommands.Limit limit = RedisZSetCommands.Limit.limit();
//        limit.offset(pageable.getOffset());
        limit.count(pageable.getPageSize());

        RedisSerializer keySerializer = redisTemplate.getKeySerializer();
        final byte[] rawKey = keySerializer.serialize(cacheName.getPrefix().concat(key));
        Set<byte[]> rawValues;
        if (isAsc) {
            rawValues = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
                public Set<byte[]> doInRedis(RedisConnection connection) {
                    return connection.zRangeByScore(rawKey, range, limit);
                }
            }, true);
        } else {
            rawValues = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
                public Set<byte[]> doInRedis(RedisConnection connection) {
                    return connection.zRevRangeByScore(rawKey, range, limit);
                }
            }, true);
        }
        RedisSerializer valueSerialize = redisTemplate.getValueSerializer();
        return SerializationUtils.deserialize(rawValues, valueSerialize);
    }

    @Override
    public Set<Object> zGetAll(CacheUtils.CacheName cacheName, String key, boolean isAsc) {
        if (cacheName.getType() != CacheUtils.CacheType.Z) {
            return null;
        }
        if (isAsc) {
            return zSetOps.range(cacheName.getPrefix().concat(key), 0, -1);
        } else {
            return zSetOps.reverseRange(cacheName.getPrefix().concat(key), 0, -1);
        }
    }

    @Override
    public Object zGetFirst(CacheUtils.CacheName cacheName, String key, boolean isAsc) {
        if (cacheName.getType() != CacheUtils.CacheType.Z) {
            return null;
        }
        Set<Object> result = null;
        if (isAsc) {
            result = zSetOps.range(cacheName.getPrefix().concat(key), 0, 0);
        } else {
            result = zSetOps.reverseRange(cacheName.getPrefix().concat(key), 0, 0);
        }
        if (CollectionUtilPlus.isNotNullOrEmpty(result)) {
            return result.iterator().next();
        } else {
            return null;
        }
    }

    @Override
    public Boolean zPut(CacheUtils.CacheName cacheName, String key, Object value, double score) {
        if (cacheName.getType() != CacheUtils.CacheType.Z) {
            return Boolean.FALSE;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Boolean result = zSetOps.add(cacheKey, value, score);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Long zSize(CacheUtils.CacheName cacheName, String key) {
        String cacheKey = cacheName.getPrefix().concat(key);
        return zSetOps.zCard(cacheKey);
    }

    @Override
    public void zRemoveByScore(CacheUtils.CacheName cacheName, String key, double minScore, double maxScore) {
        String cacheKey = cacheName.getPrefix().concat(key);
        zSetOps.removeRangeByScore(cacheKey, minScore, maxScore);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public void zRemoveByValue(CacheUtils.CacheName cacheName, String key, Object... values) {
        String cacheKey = cacheName.getPrefix().concat(key);
        zSetOps.remove(cacheKey, values);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public Long lRightPush(CacheUtils.CacheName cacheName, String key, Object... values) {
        if (CacheUtils.CacheType.L != cacheName.getType()) {
            return 0L;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(values)) {
            return 0L;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = listOps.rightPushAll(cacheKey, values);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Long lLeftPush(CacheUtils.CacheName cacheName, String key, Object... values) {
        if (CacheUtils.CacheType.L != cacheName.getType()) {
            return 0L;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(values)) {
            return 0L;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = listOps.leftPushAll(cacheKey, values);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public void lRemove(CacheUtils.CacheName cacheName, String key, Object... values) {
        if (CacheUtils.CacheType.L != cacheName.getType()) {
            return;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(values)) {
            return;
        }

        String cacheKey = cacheName.getPrefix().concat(key);
        for (Object value : values) {
            listOps.remove(cacheKey, 0, value);
        }
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public List<Object> lGet(CacheUtils.CacheName cacheName, String key, Pageable pageable) {
        if (cacheName.getType() != CacheUtils.CacheType.L) {
            return null;
        }
        long start = pageable.getOffset();
        long end = pageable.getOffset() + pageable.getPageSize() - 1;
        String cacheKey = cacheName.getPrefix().concat(key);
        List<Object> result = listOps.range(cacheKey, start, end);
        return result;
    }

    @Override
    public List<Object> lRightRangePop(CacheUtils.CacheName cacheName, String key, Integer count) {
        if (cacheName.getType() != CacheUtils.CacheType.L) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        List<Object> result = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            Object value = listOps.rightPop(cacheKey);
            if (value != null) {
                result.add(value);
            }
        }
        return result;
    }

    @Override
    public List<Object> lGetAll(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.L) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        List<Object> result = listOps.range(cacheKey, 0, -1);
        return result;
    }

    @Override
    public Object lLeftBlockPop(CacheUtils.CacheName cacheName, String key, long timeout, TimeUnit unit) {
        if (cacheName.getType() != CacheUtils.CacheType.L) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Object obj = listOps.leftPop(cacheKey, timeout, unit);
        return obj;
    }

    @Override
    public Object lLeftPop(CacheUtils.CacheName cacheName, String key) {
        String cacheKey = cacheName.getPrefix().concat(key);
        Object value = listOps.leftPop(cacheKey);
        return value;
    }

    @Override
    public Object lRightPop(CacheUtils.CacheName cacheName, String key) {
        String cacheKey = cacheName.getPrefix().concat(key);
        Object value = listOps.rightPop(cacheKey);
        return value;
    }

    @Override
    public Long lSize(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.L) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long size = listOps.size(cacheKey);
        return size;
    }

    @Override
    public void removeKey(CacheUtils.CacheName cacheName, String... keys) {
        List<String> cacheKeys = new ArrayList<>();
        for (String key : keys) {
            cacheKeys.add(cacheName.getPrefix().concat(key));
        }
        redisTemplate.delete(cacheKeys);
    }

    @Override
    public void removeKey(String... keys) {
        redisTemplate.delete(CollectionUtilPlus.asList(keys));
    }

    @Override
    public Boolean existsKey(CacheUtils.CacheName cacheName, String key) {
        String cacheKey = cacheName.getPrefix().concat(key);
        Boolean result = redisTemplate.hasKey(cacheKey);
        return result;
    }

    @Override
    public Boolean existsKey(String key) {
        Boolean result = redisTemplate.hasKey(key);
        return result;
    }

    @Override
    public Object hGet(CacheUtils.CacheName cacheName, String key, String field) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return null;
        }
        return hashOps.get(cacheName.getPrefix().concat(key), field);
    }

    @Override
    public String hGet(String key, String field) {
        HashOperations<String, String,String> hashOperations = redisTemplate.opsForHash();
        return  hashOperations.get(key, field);
    }

    @Override
    public void hPut(CacheUtils.CacheName cacheName, String key, String field, Object value) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        hashOps.put(cacheKey, field, value);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public void hPutAll(CacheUtils.CacheName cacheName, String key, Map<String, Object> map) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        hashOps.putAll(cacheKey, map);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public Long hIncrement(CacheUtils.CacheName cacheName, String key, String field, Long value) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        Long result = hashOps.increment(cacheKey, field, value == null ? 1L : value);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
        return result;
    }

    @Override
    public Map<String, Object> hGetAll(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return hashOps.entries(cacheKey);
    }

    @Override
    public Boolean hExists(CacheUtils.CacheName cacheName, String key, String field) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return null;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return hashOps.hasKey(cacheKey, field);
    }

    @Override
    public void hRemove(CacheUtils.CacheName cacheName, String key, String... fields) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return;
        }
        if (CollectionUtilPlus.isNullOrEmptyArray(fields)) {
            return;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        hashOps.delete(cacheKey, fields);
        if (cacheName.getExpiration() > 0) {
            redisTemplate.expire(cacheKey, cacheName.getExpiration(), TimeUnit.SECONDS);
        }
    }

    @Override
    public Long hSize(CacheUtils.CacheName cacheName, String key) {
        if (cacheName.getType() != CacheUtils.CacheType.H) {
            return 0L;
        }
        String cacheKey = cacheName.getPrefix().concat(key);
        return hashOps.size(cacheKey);
    }

    @Override
    public Long getRemainSecond(CacheUtils.CacheName cacheName, String key) {
        String cacheKey = cacheName.getPrefix().concat(key);
        return redisTemplate.getExpire(cacheKey);
    }

}
